// Copyright 2014 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "src/runtime/runtime-utils.h"

#include <stdlib.h>
#include <limits>

#include "src/accessors.h"
#include "src/arguments-inl.h"
#include "src/counters.h"
#include "src/debug/debug.h"
#include "src/elements.h"
#include "src/isolate-inl.h"
#include "src/log.h"
#include "src/message-template.h"
#include "src/objects/hash-table-inl.h"
#include "src/objects/literal-objects-inl.h"
#include "src/objects/smi.h"
#include "src/objects/struct-inl.h"
#include "src/runtime/runtime.h"

namespace v8 {
namespace internal {

    RUNTIME_FUNCTION(Runtime_ThrowUnsupportedSuperError)
    {
        HandleScope scope(isolate);
        DCHECK_EQ(0, args.length());
        THROW_NEW_ERROR_RETURN_FAILURE(
            isolate, NewReferenceError(MessageTemplate::kUnsupportedSuper));
    }

    RUNTIME_FUNCTION(Runtime_ThrowConstructorNonCallableError)
    {
        HandleScope scope(isolate);
        DCHECK_EQ(1, args.length());
        CONVERT_ARG_HANDLE_CHECKED(JSFunction, constructor, 0);
        Handle<String> name(constructor->shared()->Name(), isolate);
        THROW_NEW_ERROR_RETURN_FAILURE(
            isolate, NewTypeError(MessageTemplate::kConstructorNonCallable, name));
    }

    RUNTIME_FUNCTION(Runtime_ThrowStaticPrototypeError)
    {
        HandleScope scope(isolate);
        DCHECK_EQ(0, args.length());
        THROW_NEW_ERROR_RETURN_FAILURE(
            isolate, NewTypeError(MessageTemplate::kStaticPrototype));
    }

    RUNTIME_FUNCTION(Runtime_ThrowSuperAlreadyCalledError)
    {
        HandleScope scope(isolate);
        DCHECK_EQ(0, args.length());
        THROW_NEW_ERROR_RETURN_FAILURE(
            isolate, NewReferenceError(MessageTemplate::kSuperAlreadyCalled));
    }

    RUNTIME_FUNCTION(Runtime_ThrowSuperNotCalled)
    {
        HandleScope scope(isolate);
        DCHECK_EQ(0, args.length());
        THROW_NEW_ERROR_RETURN_FAILURE(
            isolate, NewReferenceError(MessageTemplate::kSuperNotCalled));
    }

    namespace {

        Object ThrowNotSuperConstructor(Isolate* isolate, Handle<Object> constructor,
            Handle<JSFunction> function)
        {
            Handle<String> super_name;
            if (constructor->IsJSFunction()) {
                super_name = handle(Handle<JSFunction>::cast(constructor)->shared()->Name(),
                    isolate);
            } else if (constructor->IsOddball()) {
                DCHECK(constructor->IsNull(isolate));
                super_name = isolate->factory()->null_string();
            } else {
                super_name = Object::NoSideEffectsToString(isolate, constructor);
            }
            // null constructor
            if (super_name->length() == 0) {
                super_name = isolate->factory()->null_string();
            }
            Handle<String> function_name(function->shared()->Name(), isolate);
            // anonymous class
            if (function_name->length() == 0) {
                THROW_NEW_ERROR_RETURN_FAILURE(
                    isolate,
                    NewTypeError(MessageTemplate::kNotSuperConstructorAnonymousClass,
                        super_name));
            }
            THROW_NEW_ERROR_RETURN_FAILURE(
                isolate, NewTypeError(MessageTemplate::kNotSuperConstructor, super_name, function_name));
        }

    } // namespace

    RUNTIME_FUNCTION(Runtime_ThrowNotSuperConstructor)
    {
        HandleScope scope(isolate);
        DCHECK_EQ(2, args.length());
        CONVERT_ARG_HANDLE_CHECKED(Object, constructor, 0);
        CONVERT_ARG_HANDLE_CHECKED(JSFunction, function, 1);
        return ThrowNotSuperConstructor(isolate, constructor, function);
    }

    RUNTIME_FUNCTION(Runtime_HomeObjectSymbol)
    {
        DCHECK_EQ(0, args.length());
        return ReadOnlyRoots(isolate).home_object_symbol();
    }

    namespace {

        template <typename Dictionary>
        Handle<Name> KeyToName(Isolate* isolate, Handle<Object> key);

        template <>
        Handle<Name> KeyToName<NameDictionary>(Isolate* isolate, Handle<Object> key)
        {
            DCHECK(key->IsName());
            return Handle<Name>::cast(key);
        }

        template <>
        Handle<Name> KeyToName<NumberDictionary>(Isolate* isolate, Handle<Object> key)
        {
            DCHECK(key->IsNumber());
            return isolate->factory()->NumberToString(key);
        }

        inline void SetHomeObject(Isolate* isolate, JSFunction method,
            JSObject home_object)
        {
            if (method->shared()->needs_home_object()) {
                const int kPropertyIndex = JSFunction::kMaybeHomeObjectDescriptorIndex;
                CHECK_EQ(method->map()->instance_descriptors()->GetKey(kPropertyIndex),
                    ReadOnlyRoots(isolate).home_object_symbol());

                FieldIndex field_index = FieldIndex::ForDescriptor(method->map(), kPropertyIndex);
                method->RawFastPropertyAtPut(field_index, home_object);
            }
        }

        // Gets |index|'th argument which may be a class constructor object, a class
        // prototype object or a class method. In the latter case the following
        // post-processing may be required:
        // 1) set [[HomeObject]] slot to given |home_object| value if the method's
        //    shared function info indicates that the method requires that;
        // 2) set method's name to a concatenation of |name_prefix| and |key| if the
        //    method's shared function info indicates that method does not have a
        //    shared name.
        template <typename Dictionary>
        MaybeHandle<Object> GetMethodAndSetHomeObjectAndName(
            Isolate* isolate, Arguments& args, Smi index, Handle<JSObject> home_object,
            Handle<String> name_prefix, Handle<Object> key)
        {
            int int_index = index.value();

            // Class constructor and prototype values do not require post processing.
            if (int_index < ClassBoilerplate::kFirstDynamicArgumentIndex) {
                return args.at<Object>(int_index);
            }

            Handle<JSFunction> method = args.at<JSFunction>(int_index);

            SetHomeObject(isolate, *method, *home_object);

            if (!method->shared()->HasSharedName()) {
                // TODO(ishell): method does not have a shared name at this point only if
                // the key is a computed property name. However, the bytecode generator
                // explicitly generates ToName bytecodes to ensure that the computed
                // property name is properly converted to Name. So, we can actually be smart
                // here and avoid converting Smi keys back to Name.
                Handle<Name> name = KeyToName<Dictionary>(isolate, key);
                if (!JSFunction::SetName(method, name, name_prefix)) {
                    return MaybeHandle<Object>();
                }
            }
            return method;
        }

        // Gets |index|'th argument which may be a class constructor object, a class
        // prototype object or a class method. In the latter case the following
        // post-processing may be required:
        // 1) set [[HomeObject]] slot to given |home_object| value if the method's
        //    shared function info indicates that the method requires that;
        // This is a simplified version of GetMethodWithSharedNameAndSetHomeObject()
        // function above that is used when it's guaranteed that the method has
        // shared name.
        Object GetMethodWithSharedNameAndSetHomeObject(Isolate* isolate,
            Arguments& args, Object index,
            JSObject home_object)
        {
            DisallowHeapAllocation no_gc;
            int int_index = Smi::ToInt(index);

            // Class constructor and prototype values do not require post processing.
            if (int_index < ClassBoilerplate::kFirstDynamicArgumentIndex) {
                return args[int_index];
            }

            Handle<JSFunction> method = args.at<JSFunction>(int_index);

            SetHomeObject(isolate, *method, home_object);

            DCHECK(method->shared()->HasSharedName());
            return *method;
        }

        template <typename Dictionary>
        Handle<Dictionary> ShallowCopyDictionaryTemplate(
            Isolate* isolate, Handle<Dictionary> dictionary_template)
        {
            Handle<Map> dictionary_map(dictionary_template->map(), isolate);
            Handle<Dictionary> dictionary = Handle<Dictionary>::cast(isolate->factory()->CopyFixedArrayWithMap(
                dictionary_template, dictionary_map));
            // Clone all AccessorPairs in the dictionary.
            int capacity = dictionary->Capacity();
            for (int i = 0; i < capacity; i++) {
                Object value = dictionary->ValueAt(i);
                if (value->IsAccessorPair()) {
                    Handle<AccessorPair> pair(AccessorPair::cast(value), isolate);
                    pair = AccessorPair::Copy(isolate, pair);
                    dictionary->ValueAtPut(i, *pair);
                }
            }
            return dictionary;
        }

        template <typename Dictionary>
        bool SubstituteValues(Isolate* isolate, Handle<Dictionary> dictionary,
            Handle<JSObject> receiver, Arguments& args,
            bool* install_name_accessor = nullptr)
        {
            Handle<Name> name_string = isolate->factory()->name_string();

            // Replace all indices with proper methods.
            int capacity = dictionary->Capacity();
            ReadOnlyRoots roots(isolate);
            for (int i = 0; i < capacity; i++) {
                Object maybe_key = dictionary->KeyAt(i);
                if (!Dictionary::IsKey(roots, maybe_key))
                    continue;
                if (install_name_accessor && *install_name_accessor && (maybe_key == *name_string)) {
                    *install_name_accessor = false;
                }
                Handle<Object> key(maybe_key, isolate);
                Handle<Object> value(dictionary->ValueAt(i), isolate);
                if (value->IsAccessorPair()) {
                    Handle<AccessorPair> pair = Handle<AccessorPair>::cast(value);
                    Object tmp = pair->getter();
                    if (tmp->IsSmi()) {
                        Handle<Object> result;
                        ASSIGN_RETURN_ON_EXCEPTION_VALUE(
                            isolate, result,
                            GetMethodAndSetHomeObjectAndName<Dictionary>(
                                isolate, args, Smi::cast(tmp), receiver,
                                isolate->factory()->get_string(), key),
                            false);
                        pair->set_getter(*result);
                    }
                    tmp = pair->setter();
                    if (tmp->IsSmi()) {
                        Handle<Object> result;
                        ASSIGN_RETURN_ON_EXCEPTION_VALUE(
                            isolate, result,
                            GetMethodAndSetHomeObjectAndName<Dictionary>(
                                isolate, args, Smi::cast(tmp), receiver,
                                isolate->factory()->set_string(), key),
                            false);
                        pair->set_setter(*result);
                    }
                } else if (value->IsSmi()) {
                    Handle<Object> result;
                    ASSIGN_RETURN_ON_EXCEPTION_VALUE(
                        isolate, result,
                        GetMethodAndSetHomeObjectAndName<Dictionary>(
                            isolate, args, Smi::cast(*value), receiver,
                            isolate->factory()->empty_string(), key),
                        false);
                    dictionary->ValueAtPut(i, *result);
                }
            }
            return true;
        }

        bool AddDescriptorsByTemplate(
            Isolate* isolate, Handle<Map> map,
            Handle<DescriptorArray> descriptors_template,
            Handle<NumberDictionary> elements_dictionary_template,
            Handle<JSObject> receiver, Arguments& args)
        {
            int nof_descriptors = descriptors_template->number_of_descriptors();

            Handle<DescriptorArray> descriptors = DescriptorArray::Allocate(isolate, nof_descriptors, 0);

            Handle<NumberDictionary> elements_dictionary = *elements_dictionary_template == ReadOnlyRoots(isolate).empty_slow_element_dictionary()
                ? elements_dictionary_template
                : ShallowCopyDictionaryTemplate(isolate,
                    elements_dictionary_template);

            Handle<PropertyArray> property_array = isolate->factory()->empty_property_array();
            if (FLAG_track_constant_fields) {
                // If we store constants in instances, count the number of properties
                // that must be in the instance and create the property array to
                // hold the constants.
                int count = 0;
                for (int i = 0; i < nof_descriptors; i++) {
                    PropertyDetails details = descriptors_template->GetDetails(i);
                    if (details.location() == kDescriptor && details.kind() == kData) {
                        count++;
                    }
                }
                property_array = isolate->factory()->NewPropertyArray(count);
            }

            // Read values from |descriptors_template| and store possibly post-processed
            // values into "instantiated" |descriptors| array.
            int field_index = 0;
            for (int i = 0; i < nof_descriptors; i++) {
                Object value = descriptors_template->GetStrongValue(i);
                if (value->IsAccessorPair()) {
                    Handle<AccessorPair> pair = AccessorPair::Copy(
                        isolate, handle(AccessorPair::cast(value), isolate));
                    value = *pair;
                }
                DisallowHeapAllocation no_gc;
                Name name = descriptors_template->GetKey(i);
                DCHECK(name->IsUniqueName());
                PropertyDetails details = descriptors_template->GetDetails(i);
                if (details.location() == kDescriptor) {
                    if (details.kind() == kData) {
                        if (value->IsSmi()) {
                            value = GetMethodWithSharedNameAndSetHomeObject(isolate, args, value,
                                *receiver);
                        }
                        details = details.CopyWithRepresentation(value->OptimalRepresentation());
                    } else {
                        DCHECK_EQ(kAccessor, details.kind());
                        if (value->IsAccessorPair()) {
                            AccessorPair pair = AccessorPair::cast(value);
                            Object tmp = pair->getter();
                            if (tmp->IsSmi()) {
                                pair->set_getter(GetMethodWithSharedNameAndSetHomeObject(
                                    isolate, args, tmp, *receiver));
                            }
                            tmp = pair->setter();
                            if (tmp->IsSmi()) {
                                pair->set_setter(GetMethodWithSharedNameAndSetHomeObject(
                                    isolate, args, tmp, *receiver));
                            }
                        }
                    }
                } else {
                    UNREACHABLE();
                }
                DCHECK(value->FitsRepresentation(details.representation()));
                // With constant field tracking, we store the values in the instance.
                if (FLAG_track_constant_fields && details.location() == kDescriptor && details.kind() == kData) {
                    details = PropertyDetails(details.kind(), details.attributes(), kField,
                        PropertyConstness::kConst,
                        details.representation(), field_index)
                                  .set_pointer(details.pointer());

                    property_array->set(field_index, value);
                    field_index++;
                    descriptors->Set(i, name, MaybeObject::FromObject(FieldType::Any()),
                        details);
                } else {
                    descriptors->Set(i, name, MaybeObject::FromObject(value), details);
                }
            }

            map->InitializeDescriptors(isolate, *descriptors,
                LayoutDescriptor::FastPointerLayout());
            if (elements_dictionary->NumberOfElements() > 0) {
                if (!SubstituteValues<NumberDictionary>(isolate, elements_dictionary,
                        receiver, args)) {
                    return false;
                }
                map->set_elements_kind(DICTIONARY_ELEMENTS);
            }

            // Atomically commit the changes.
            receiver->synchronized_set_map(*map);
            if (elements_dictionary->NumberOfElements() > 0) {
                receiver->set_elements(*elements_dictionary);
            }
            if (property_array->length() > 0) {
                receiver->SetProperties(*property_array);
            }
            return true;
        }

        bool AddDescriptorsByTemplate(
            Isolate* isolate, Handle<Map> map,
            Handle<NameDictionary> properties_dictionary_template,
            Handle<NumberDictionary> elements_dictionary_template,
            Handle<FixedArray> computed_properties, Handle<JSObject> receiver,
            bool install_name_accessor, Arguments& args)
        {
            int computed_properties_length = computed_properties->length();

            // Shallow-copy properties template.
            Handle<NameDictionary> properties_dictionary = ShallowCopyDictionaryTemplate(isolate, properties_dictionary_template);
            Handle<NumberDictionary> elements_dictionary = ShallowCopyDictionaryTemplate(isolate, elements_dictionary_template);

            typedef ClassBoilerplate::ValueKind ValueKind;
            typedef ClassBoilerplate::ComputedEntryFlags ComputedEntryFlags;

            // Merge computed properties with properties and elements dictionary
            // templates.
            int i = 0;
            while (i < computed_properties_length) {
                int flags = Smi::ToInt(computed_properties->get(i++));

                ValueKind value_kind = ComputedEntryFlags::ValueKindBits::decode(flags);
                int key_index = ComputedEntryFlags::KeyIndexBits::decode(flags);
                Object value = Smi::FromInt(key_index + 1); // Value follows name.

                Handle<Object> key = args.at<Object>(key_index);
                DCHECK(key->IsName());
                uint32_t element;
                Handle<Name> name = Handle<Name>::cast(key);
                if (name->AsArrayIndex(&element)) {
                    ClassBoilerplate::AddToElementsTemplate(
                        isolate, elements_dictionary, element, key_index, value_kind, value);

                } else {
                    name = isolate->factory()->InternalizeName(name);
                    ClassBoilerplate::AddToPropertiesTemplate(
                        isolate, properties_dictionary, name, key_index, value_kind, value);
                }
            }

            // Replace all indices with proper methods.
            if (!SubstituteValues<NameDictionary>(isolate, properties_dictionary,
                    receiver, args,
                    &install_name_accessor)) {
                return false;
            }
            if (install_name_accessor) {
                PropertyAttributes attribs = static_cast<PropertyAttributes>(DONT_ENUM | READ_ONLY);
                PropertyDetails details(kAccessor, attribs, PropertyCellType::kNoCell);
                Handle<NameDictionary> dict = NameDictionary::Add(
                    isolate, properties_dictionary, isolate->factory()->name_string(),
                    isolate->factory()->function_name_accessor(), details);
                CHECK_EQ(*dict, *properties_dictionary);
            }

            if (elements_dictionary->NumberOfElements() > 0) {
                if (!SubstituteValues<NumberDictionary>(isolate, elements_dictionary,
                        receiver, args)) {
                    return false;
                }
                map->set_elements_kind(DICTIONARY_ELEMENTS);
            }

            // Atomically commit the changes.
            receiver->synchronized_set_map(*map);
            receiver->set_raw_properties_or_hash(*properties_dictionary);
            if (elements_dictionary->NumberOfElements() > 0) {
                receiver->set_elements(*elements_dictionary);
            }
            return true;
        }

        Handle<JSObject> CreateClassPrototype(Isolate* isolate)
        {
            Factory* factory = isolate->factory();

            const int kInobjectFields = 0;

            Handle<Map> map;
            if (FLAG_track_constant_fields) {
                // For constant tracking we want to avoid tha hassle of handling
                // in-object properties, so create a map with no in-object
                // properties.

                // TODO(ishell) Support caching of zero in-object properties map
                // by ObjectLiteralMapFromCache().
                map = Map::Create(isolate, 0);
            } else {
                // Just use some JSObject map of certain size.
                map = factory->ObjectLiteralMapFromCache(isolate->native_context(),
                    kInobjectFields);
            }

            return factory->NewJSObjectFromMap(map);
        }

        bool InitClassPrototype(Isolate* isolate,
            Handle<ClassBoilerplate> class_boilerplate,
            Handle<JSObject> prototype,
            Handle<HeapObject> prototype_parent,
            Handle<JSFunction> constructor, Arguments& args)
        {
            Handle<Map> map(prototype->map(), isolate);
            map = Map::CopyDropDescriptors(isolate, map);
            map->set_is_prototype_map(true);
            Map::SetPrototype(isolate, map, prototype_parent);
            constructor->set_prototype_or_initial_map(*prototype);
            map->SetConstructor(*constructor);
            Handle<FixedArray> computed_properties(
                class_boilerplate->instance_computed_properties(), isolate);
            Handle<NumberDictionary> elements_dictionary_template(
                NumberDictionary::cast(class_boilerplate->instance_elements_template()),
                isolate);

            Handle<Object> properties_template(
                class_boilerplate->instance_properties_template(), isolate);
            if (properties_template->IsNameDictionary()) {
                Handle<NameDictionary> properties_dictionary_template = Handle<NameDictionary>::cast(properties_template);

                map->set_is_dictionary_map(true);
                map->set_is_migration_target(false);
                map->set_may_have_interesting_symbols(true);
                map->set_construction_counter(Map::kNoSlackTracking);

                // We care about name property only for class constructor.
                const bool install_name_accessor = false;

                return AddDescriptorsByTemplate(
                    isolate, map, properties_dictionary_template,
                    elements_dictionary_template, computed_properties, prototype,
                    install_name_accessor, args);
            } else {
                Handle<DescriptorArray> descriptors_template = Handle<DescriptorArray>::cast(properties_template);

                // The size of the prototype object is known at this point.
                // So we can create it now and then add the rest instance methods to the
                // map.
                return AddDescriptorsByTemplate(isolate, map, descriptors_template,
                    elements_dictionary_template, prototype,
                    args);
            }
        }

        bool InitClassConstructor(Isolate* isolate,
            Handle<ClassBoilerplate> class_boilerplate,
            Handle<HeapObject> constructor_parent,
            Handle<JSFunction> constructor, Arguments& args)
        {
            Handle<Map> map(constructor->map(), isolate);
            map = Map::CopyDropDescriptors(isolate, map);
            DCHECK(map->is_prototype_map());

            if (!constructor_parent.is_null()) {
                // Set map's prototype without enabling prototype setup mode for superclass
                // because it does not make sense.
                Map::SetPrototype(isolate, map, constructor_parent, false);
            }

            Handle<NumberDictionary> elements_dictionary_template(
                NumberDictionary::cast(class_boilerplate->static_elements_template()),
                isolate);
            Handle<FixedArray> computed_properties(
                class_boilerplate->static_computed_properties(), isolate);

            Handle<Object> properties_template(
                class_boilerplate->static_properties_template(), isolate);

            if (properties_template->IsNameDictionary()) {
                Handle<NameDictionary> properties_dictionary_template = Handle<NameDictionary>::cast(properties_template);

                map->set_is_dictionary_map(true);
                map->InitializeDescriptors(isolate,
                    ReadOnlyRoots(isolate).empty_descriptor_array(),
                    LayoutDescriptor::FastPointerLayout());
                map->set_is_migration_target(false);
                map->set_may_have_interesting_symbols(true);
                map->set_construction_counter(Map::kNoSlackTracking);

                bool install_name_accessor = class_boilerplate->install_class_name_accessor() != 0;

                return AddDescriptorsByTemplate(
                    isolate, map, properties_dictionary_template,
                    elements_dictionary_template, computed_properties, constructor,
                    install_name_accessor, args);
            } else {
                Handle<DescriptorArray> descriptors_template = Handle<DescriptorArray>::cast(properties_template);

                return AddDescriptorsByTemplate(isolate, map, descriptors_template,
                    elements_dictionary_template, constructor,
                    args);
            }
        }

        MaybeHandle<Object> DefineClass(Isolate* isolate,
            Handle<ClassBoilerplate> class_boilerplate,
            Handle<Object> super_class,
            Handle<JSFunction> constructor,
            Arguments& args)
        {
            Handle<Object> prototype_parent;
            Handle<HeapObject> constructor_parent;

            if (super_class->IsTheHole(isolate)) {
                prototype_parent = isolate->initial_object_prototype();
            } else {
                if (super_class->IsNull(isolate)) {
                    prototype_parent = isolate->factory()->null_value();
                } else if (super_class->IsConstructor()) {
                    DCHECK(!super_class->IsJSFunction() || !IsResumableFunction(Handle<JSFunction>::cast(super_class)->shared()->kind()));
                    ASSIGN_RETURN_ON_EXCEPTION(
                        isolate, prototype_parent,
                        Runtime::GetObjectProperty(isolate, super_class,
                            isolate->factory()->prototype_string()),
                        Object);
                    if (!prototype_parent->IsNull(isolate) && !prototype_parent->IsJSReceiver()) {
                        THROW_NEW_ERROR(
                            isolate, NewTypeError(MessageTemplate::kPrototypeParentNotAnObject, prototype_parent),
                            Object);
                    }
                    // Create new handle to avoid |constructor_parent| corruption because of
                    // |super_class| handle value overwriting via storing to
                    // args[ClassBoilerplate::kPrototypeArgumentIndex] below.
                    constructor_parent = handle(HeapObject::cast(*super_class), isolate);
                } else {
                    THROW_NEW_ERROR(isolate,
                        NewTypeError(MessageTemplate::kExtendsValueNotConstructor,
                            super_class),
                        Object);
                }
            }

            Handle<JSObject> prototype = CreateClassPrototype(isolate);
            DCHECK_EQ(*constructor, args[ClassBoilerplate::kConstructorArgumentIndex]);
            args.set_at(ClassBoilerplate::kPrototypeArgumentIndex, *prototype);

            if (!InitClassConstructor(isolate, class_boilerplate, constructor_parent,
                    constructor, args)
                || !InitClassPrototype(isolate, class_boilerplate, prototype,
                    Handle<HeapObject>::cast(prototype_parent),
                    constructor, args)) {
                DCHECK(isolate->has_pending_exception());
                return MaybeHandle<Object>();
            }
            if (FLAG_trace_maps) {
                LOG(isolate,
                    MapEvent("InitialMap", Map(), constructor->map(),
                        "init class constructor", constructor->shared()->DebugName()));
                LOG(isolate, MapEvent("InitialMap", Map(), prototype->map(), "init class prototype"));
            }

            return prototype;
        }

    } // namespace

    RUNTIME_FUNCTION(Runtime_DefineClass)
    {
        HandleScope scope(isolate);
        DCHECK_LE(ClassBoilerplate::kFirstDynamicArgumentIndex, args.length());
        CONVERT_ARG_HANDLE_CHECKED(ClassBoilerplate, class_boilerplate, 0);
        CONVERT_ARG_HANDLE_CHECKED(JSFunction, constructor, 1);
        CONVERT_ARG_HANDLE_CHECKED(Object, super_class, 2);
        DCHECK_EQ(class_boilerplate->arguments_count(), args.length());

        RETURN_RESULT_OR_FAILURE(
            isolate,
            DefineClass(isolate, class_boilerplate, super_class, constructor, args));
    }

    namespace {

        enum class SuperMode { kLoad,
            kStore };

        MaybeHandle<JSReceiver> GetSuperHolder(
            Isolate* isolate, Handle<Object> receiver, Handle<JSObject> home_object,
            SuperMode mode, MaybeHandle<Name> maybe_name, uint32_t index)
        {
            if (home_object->IsAccessCheckNeeded() && !isolate->MayAccess(handle(isolate->context(), isolate), home_object)) {
                isolate->ReportFailedAccessCheck(home_object);
                RETURN_EXCEPTION_IF_SCHEDULED_EXCEPTION(isolate, JSReceiver);
            }

            PrototypeIterator iter(isolate, home_object);
            Handle<Object> proto = PrototypeIterator::GetCurrent(iter);
            if (!proto->IsJSReceiver()) {
                MessageTemplate message = mode == SuperMode::kLoad
                    ? MessageTemplate::kNonObjectPropertyLoad
                    : MessageTemplate::kNonObjectPropertyStore;
                Handle<Name> name;
                if (!maybe_name.ToHandle(&name)) {
                    name = isolate->factory()->Uint32ToString(index);
                }
                THROW_NEW_ERROR(isolate, NewTypeError(message, name, proto), JSReceiver);
            }
            return Handle<JSReceiver>::cast(proto);
        }

        MaybeHandle<Object> LoadFromSuper(Isolate* isolate, Handle<Object> receiver,
            Handle<JSObject> home_object,
            Handle<Name> name)
        {
            Handle<JSReceiver> holder;
            ASSIGN_RETURN_ON_EXCEPTION(
                isolate, holder,
                GetSuperHolder(isolate, receiver, home_object, SuperMode::kLoad, name, 0),
                Object);
            LookupIterator it(receiver, name, holder);
            Handle<Object> result;
            ASSIGN_RETURN_ON_EXCEPTION(isolate, result, Object::GetProperty(&it), Object);
            return result;
        }

        MaybeHandle<Object> LoadElementFromSuper(Isolate* isolate,
            Handle<Object> receiver,
            Handle<JSObject> home_object,
            uint32_t index)
        {
            Handle<JSReceiver> holder;
            ASSIGN_RETURN_ON_EXCEPTION(
                isolate, holder,
                GetSuperHolder(isolate, receiver, home_object, SuperMode::kLoad,
                    MaybeHandle<Name>(), index),
                Object);
            LookupIterator it(isolate, receiver, index, holder);
            Handle<Object> result;
            ASSIGN_RETURN_ON_EXCEPTION(isolate, result, Object::GetProperty(&it), Object);
            return result;
        }

    } // anonymous namespace

    RUNTIME_FUNCTION(Runtime_LoadFromSuper)
    {
        HandleScope scope(isolate);
        DCHECK_EQ(3, args.length());
        CONVERT_ARG_HANDLE_CHECKED(Object, receiver, 0);
        CONVERT_ARG_HANDLE_CHECKED(JSObject, home_object, 1);
        CONVERT_ARG_HANDLE_CHECKED(Name, name, 2);

        RETURN_RESULT_OR_FAILURE(isolate,
            LoadFromSuper(isolate, receiver, home_object, name));
    }

    RUNTIME_FUNCTION(Runtime_LoadKeyedFromSuper)
    {
        HandleScope scope(isolate);
        DCHECK_EQ(3, args.length());
        CONVERT_ARG_HANDLE_CHECKED(Object, receiver, 0);
        CONVERT_ARG_HANDLE_CHECKED(JSObject, home_object, 1);
        CONVERT_ARG_HANDLE_CHECKED(Object, key, 2);

        uint32_t index = 0;

        if (key->ToArrayIndex(&index)) {
            RETURN_RESULT_OR_FAILURE(
                isolate, LoadElementFromSuper(isolate, receiver, home_object, index));
        }

        Handle<Name> name;
        ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, name,
            Object::ToName(isolate, key));
        // TODO(verwaest): Unify using LookupIterator.
        if (name->AsArrayIndex(&index)) {
            RETURN_RESULT_OR_FAILURE(
                isolate, LoadElementFromSuper(isolate, receiver, home_object, index));
        }
        RETURN_RESULT_OR_FAILURE(isolate,
            LoadFromSuper(isolate, receiver, home_object, name));
    }

    namespace {

        MaybeHandle<Object> StoreToSuper(Isolate* isolate, Handle<JSObject> home_object,
            Handle<Object> receiver, Handle<Name> name,
            Handle<Object> value)
        {
            Handle<JSReceiver> holder;
            ASSIGN_RETURN_ON_EXCEPTION(isolate, holder,
                GetSuperHolder(isolate, receiver, home_object,
                    SuperMode::kStore, name, 0),
                Object);
            LookupIterator it(receiver, name, holder);
            MAYBE_RETURN(Object::SetSuperProperty(&it, value, StoreOrigin::kNamed),
                MaybeHandle<Object>());
            return value;
        }

        MaybeHandle<Object> StoreElementToSuper(Isolate* isolate,
            Handle<JSObject> home_object,
            Handle<Object> receiver, uint32_t index,
            Handle<Object> value)
        {
            Handle<JSReceiver> holder;
            ASSIGN_RETURN_ON_EXCEPTION(
                isolate, holder,
                GetSuperHolder(isolate, receiver, home_object, SuperMode::kStore,
                    MaybeHandle<Name>(), index),
                Object);
            LookupIterator it(isolate, receiver, index, holder);
            MAYBE_RETURN(Object::SetSuperProperty(&it, value, StoreOrigin::kMaybeKeyed),
                MaybeHandle<Object>());
            return value;
        }

    } // anonymous namespace

    RUNTIME_FUNCTION(Runtime_StoreToSuper)
    {
        HandleScope scope(isolate);
        DCHECK_EQ(4, args.length());
        CONVERT_ARG_HANDLE_CHECKED(Object, receiver, 0);
        CONVERT_ARG_HANDLE_CHECKED(JSObject, home_object, 1);
        CONVERT_ARG_HANDLE_CHECKED(Name, name, 2);
        CONVERT_ARG_HANDLE_CHECKED(Object, value, 3);

        RETURN_RESULT_OR_FAILURE(
            isolate, StoreToSuper(isolate, home_object, receiver, name, value));
    }

    static MaybeHandle<Object> StoreKeyedToSuper(Isolate* isolate,
        Handle<JSObject> home_object,
        Handle<Object> receiver,
        Handle<Object> key,
        Handle<Object> value)
    {
        uint32_t index = 0;

        if (key->ToArrayIndex(&index)) {
            return StoreElementToSuper(isolate, home_object, receiver, index, value);
        }
        Handle<Name> name;
        ASSIGN_RETURN_ON_EXCEPTION(isolate, name, Object::ToName(isolate, key),
            Object);
        // TODO(verwaest): Unify using LookupIterator.
        if (name->AsArrayIndex(&index)) {
            return StoreElementToSuper(isolate, home_object, receiver, index, value);
        }
        return StoreToSuper(isolate, home_object, receiver, name, value);
    }

    RUNTIME_FUNCTION(Runtime_StoreKeyedToSuper)
    {
        HandleScope scope(isolate);
        DCHECK_EQ(4, args.length());
        CONVERT_ARG_HANDLE_CHECKED(Object, receiver, 0);
        CONVERT_ARG_HANDLE_CHECKED(JSObject, home_object, 1);
        CONVERT_ARG_HANDLE_CHECKED(Object, key, 2);
        CONVERT_ARG_HANDLE_CHECKED(Object, value, 3);

        RETURN_RESULT_OR_FAILURE(
            isolate, StoreKeyedToSuper(isolate, home_object, receiver, key, value));
    }

} // namespace internal
} // namespace v8
